/*
 * $Id: PropertiesConfigurator.java 165 2013-01-29 11:12:24Z vladimirow@mail.ru $
 * $HeadURL: http://ultralog.googlecode.com/svn/ultralog/trunk/src/java/com/google/code/ultralog/configuration/PropertiesConfigurator.java $
 *
 * This file is a part of ultralog project (http://code.google.com/p/ultralog/).
 * Released under New BSD License.
 */
package com.google.code.ultralog.configuration;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import com.google.code.ultralog.Logger;
import com.google.code.ultralog.LoggerManager;
import com.google.code.ultralog.factory.LoggerInterfaceDescriptor;
import com.google.code.ultralog.factory.LoggerInterfaceDescriptor.LogLevelDescriptor;
import com.google.code.ultralog.factory.LoggerInterfaceUtils;
import com.google.code.ultralog.formatter.gfree.GarbageFreeFormatterFactory;
import com.google.code.ultralog.template.TemplateUtils;
import com.google.code.ultralog.writer.CharacterOutputStreamLogRecordWriter;
import com.google.code.ultralog.writer.GarbageFreeOutputStreamWriter;
import com.google.code.ultralog.writer.LogRecordWriter;
import com.google.code.ultralog.writer.TemplateLogRecordFormatter;
import com.google.code.ultralog.writer.WriterCharacterOutputStream;

/**
 * An utility class that allows configuring {@link LoggerManager} based on
 * settings stored in {@link Properties} object.
 *
 * @author Mikhail Vladimirov
 */
public class PropertiesConfigurator
{
    private final LoggerConfigurationProvider configurationProvider;

    /**
     * Create new configurator that uses information stored in given
     * {@link Properties} object.
     *
     * @param properties {@link Properties} object to use information from
     */
    public PropertiesConfigurator (Properties properties)
    {
        if (properties == null)
            throw new IllegalArgumentException ("Properties is null");

        String writersList = properties.getProperty ("writers", "").trim ();

        String [] writerPrefixes;
        if (writersList.isEmpty ())
            writerPrefixes = new String [] {"writer"};
        else
        {
            String [] writers = writersList.split ("\\s+");

            int count = writers.length;
            writerPrefixes = new String [count];

            for (int i = 0; i < count; i++)
                writerPrefixes [i] = "writer." + writers [i];
        }

        List <LoggerConfigurationProvider> providers =
            new ArrayList <LoggerConfigurationProvider> ();
        for (String writerPrefix: writerPrefixes)
        {
            try
            {
                providers.add (
                    createConfigurationProvider (
                        properties, writerPrefix));
            }
            catch (Exception ex)
            {
                System.err.println (
                    "Failed to configure writer: " + writerPrefix);
                ex.printStackTrace ();
            }
        }

        int count = providers.size ();
        if (count == 0)
            throw new IllegalArgumentException (
                "Initialization of all writers failed");
        else if (count == 1)
            configurationProvider = providers.get (0);
        else
            configurationProvider =
                new CompositeLoggerConfigurationProvider (
                    providers.toArray (
                        new LoggerConfigurationProvider [count]));
    }

    /**
     * Get configuration provider based on properties.
     *
     * @return configuration provider
     */
    public LoggerConfigurationProvider getLoggerConfigurationProvider ()
    {
        return configurationProvider;
    }

    /**
     * Configure {@link LoggerManager} based on information stored in given
     * {@link Properties} object.
     *
     * @param properties {@link Properties} object to use information from
     */
    public static void configure (Properties properties)
    {
        if (properties == null)
            throw new IllegalArgumentException ("Properties is null");

        PropertiesConfigurator configurator =
            new PropertiesConfigurator (properties);

        LoggerManager.setLoggerConfigurationProvider (
            configurator.getLoggerConfigurationProvider ());
    }

    /**
     * Configure {@link LoggerManager} based on information stored in given
     * properties file.
     *
     * @param file properties file to use information from
     * @throws IOException if reading from file failed
     */
    public static void configure (File file) throws IOException
    {
        if (file == null)
            throw new IllegalArgumentException ("File name is null");

        InputStream input = new FileInputStream (file);
        try
        {
            Properties properties = new Properties ();
            properties.load (input);
            configure (properties);
        }
        finally
        {
            input.close ();
        }
    }

    @SuppressWarnings("unchecked")
    private static LoggerConfigurationProvider createConfigurationProvider (
        Properties properties, String prefix)
        throws Exception
    {
        if (properties == null)
            throw new IllegalArgumentException ("Properties is null");

        OutputStream output;

        String type = properties.getProperty (
            prefix + ".type", "stdout");
        if ("stdout".equals (type))
            output = System.out;
        else if ("stderr".equals (type))
            output = System.err;
        else if ("file".equals (type))
        {
            String fileNameProperty = prefix + ".fileName";
            String fileName = properties.getProperty (
                fileNameProperty);
            if (fileName == null)
                throw new IllegalArgumentException (
                    "Missing property: " + fileNameProperty);

            output = new FileOutputStream (fileName, true);
        }
        else throw new IllegalArgumentException (
            "Unsupported writer type: " + type);

        String pattern = properties.getProperty (
            prefix + ".pattern",
            "${levelName} ${timestamp:T%Y-%m-%d %H:%i:%s.%F} " +
            "[${threadName}] ${logger}: ${message}\n");

        LogRecordWriter logRecordWriter =
            new CharacterOutputStreamLogRecordWriter (
                new WriterCharacterOutputStream (
                    new GarbageFreeOutputStreamWriter (output)),
                new TemplateLogRecordFormatter (
                    TemplateUtils.parseTemplate (
                        pattern),
                    GarbageFreeFormatterFactory.INSTANCE));

        String baseInterface =
            properties.getProperty (
                prefix + ".baseInterface",
                "com.google.code.ultralog.BasicLogger");

        Class <?> baseInterfaceClass = Class.forName (baseInterface);
        if (!Logger.class.isAssignableFrom (baseInterfaceClass))
            throw new IllegalArgumentException (
                "Base interface does not extend Logger: " +
                baseInterfaceClass.getName ());
        LoggerInterfaceDescriptor baseInterfaceDescriptor =
            LoggerInterfaceUtils.
                describeLoggerInterface (
                    (Class <? extends Logger>)baseInterfaceClass);

        Map <String, Integer> levelNames = new HashMap <String, Integer> ();

        int levelCount = baseInterfaceDescriptor.getLogLevelCount ();
        for (int i = 0; i < levelCount; i++)
        {
            LogLevelDescriptor levelDescriptor =
                baseInterfaceDescriptor.getLogLevelAt (i);

            levelNames.put (
                levelDescriptor.getName (),
                Integer.valueOf (levelDescriptor.getLevel ()));
        }

        String defaultLevelString =
            properties.getProperty (prefix + ".level", "0").trim ();
        Integer defaultLevel = levelNames.get (defaultLevelString);
        if (defaultLevel == null)
            defaultLevel = Integer.valueOf (defaultLevelString);

        List <String> prefixes = new ArrayList <String> ();
        List <LoggerConfiguration> configurations =
            new ArrayList <LoggerConfiguration> ();

        String levelPrefix = prefix + ".level.";
        int levelPrefixLength = levelPrefix.length ();
        for (Object key: properties.keySet ())
        {
            String keyString = key.toString ();

            if (keyString.startsWith (levelPrefix))
            {
                String loggerNamePrefix =
                    keyString.substring (levelPrefixLength);
                String levelString = properties.getProperty (keyString).trim ();
                Integer level = levelNames.get (levelString);
                if (level == null)
                    level = Integer.valueOf (levelString);

                prefixes.add (loggerNamePrefix);
                configurations.add (
                    new SimpleLoggerConfiguration (
                        level.intValue (), logRecordWriter));
            }
        }

        return new NameBasedLoggerConfigurationProvider (
            new SimpleLoggerConfiguration (
                defaultLevel.intValue (), logRecordWriter),
            prefixes.toArray (new String [prefixes.size ()]),
            configurations.toArray (
                new LoggerConfiguration [configurations.size ()]));
    }
}
